/*
* ============================================================================
*  Name        : sea_object.cpp
*  Part of     : Finite state machine utilities
*  Description : Object implementation.
*  Version     : %version: %
*
*
* ============================================================================
*/

#include "sea_object.h"
#include "sea_link.h"
#include "sea_trace.h"
#include "sea_memtracker.h"
#include "sea_iterator.h"

/**
 * @file sea_object.cpp
 * @brief
 *  Base object.
 *  It can be linked with other object by a number of qualities/associations. See sea::Association. <br>
 *
 * @li Knowledge is a collection of stored and retained information
 * @li Intelligence is an abilty to find associations between things in sorted form by the likelyhood in a particular
 *     context
 * @li Things are connected == linked with each other by qualities == properties == associations
 * @li One thing is connected to another via a number of associations
 * @li Agregation mechanisms of things and associations are the same
 * @li Objects and associations are dynamic in nature and may change in time
 * that is depicted by the finite state machine notation presented in the documenation
 *
 * @ingroup AssociationUtilities
 *
 */
using namespace sea;

// -------------------------------------------------------------------------------------------------

Association Object::m_any_id;

#if defined( ASSOCIATIONS_MEMORY_TEST )

#define T_SZ ( 10000 * 1024 )

static char* g_memory = 0;
static int g_allocated = 0;

#endif

// -------------------------------------------------------------------------------------------------
// Overloaded no throw new
// -------------------------------------------------------------------------------------------------

#if defined( DEBUG ) && defined( MEM_TRACKING )
    void* Object::operator new( unsigned int size, int line, const char* file ) NOTHROW
#else
    inline void* Object::operator new( unsigned int size ) NOTHROW
#endif
{
#if defined( ASSOCIATIONS_MEMORY_TEST )
    if ( g_memory == 0 || g_allocated + size > T_SZ )
    {
        g_memory = ( char* )malloc( T_SZ );
        ASSERT_DEBUG( g_memory );
        g_allocated = 0;
    }

    void* ret = g_memory;
    g_memory += size;
    g_allocated += size;
#else
    void* ret = ::new unsigned char[ size ];

#if defined( DEBUG ) && defined( MEM_TRACKING )
    MemoryBlock::add( ret, size, line, file );
#endif

#endif

    ASSERT_DEBUG( ret );
    return ret;
}

// -------------------------------------------------------------------------------------------------
// Overloaded no throw new[]
// -------------------------------------------------------------------------------------------------
#if defined( DEBUG ) && defined( MEM_TRACKING )
    void* Object::operator new[]( unsigned int size, int line, const char* file ) NOTHROW
#else
    inline void* Object::operator new[]( unsigned int size ) NOTHROW
#endif
{
#if defined( ASSOCIATIONS_MEMORY_TEST )
    if ( g_memory == 0 || g_allocated + size > T_SZ )
    {
        g_memory = ( char* )malloc( T_SZ );
        ASSERT_DEBUG( false );
        g_allocated = 0;
    }

    void* ret = g_memory;
    g_memory += size;
    g_allocated += size;
#else
    void* ret = ::new unsigned char[ size ];

#if defined( DEBUG ) && defined( MEM_TRACKING )
    MemoryBlock::add( ret, size, line, file );
#endif

#endif

    ASSERT_DEBUG( ret );
    return ret;
}

// -------------------------------------------------------------------------------------------------
// Overloaded no throw delete
// -------------------------------------------------------------------------------------------------
void Object::operator delete( void* ptr ) NOTHROW
{
    SEA_UNUSED( ptr );
#if !defined( ASSOCIATIONS_MEMORY_TEST )

#if defined( DEBUG ) && defined( MEM_TRACKING )
    MemoryBlock::remove( ptr );
#endif
    ASSERT_DEBUG( ptr );

    // Cast to avoid gcc warning
    ::delete reinterpret_cast< char* >( ptr );
#endif
}

// -------------------------------------------------------------------------------------------------
// Overloaded no throw delete[]
// -------------------------------------------------------------------------------------------------
void Object::operator delete[ ]( void* ptr ) NOTHROW
{
    SEA_UNUSED( ptr );
#if !defined( ASSOCIATIONS_MEMORY_TEST )

#if defined( DEBUG ) && defined( MEM_TRACKING )
    MemoryBlock::remove( ptr );
#endif
    ASSERT_DEBUG( ptr );

    // Cast to avoid gcc warning
    ::delete reinterpret_cast< char* >( ptr );
#endif
}

// -------------------------------------------------------------------------------------------------
// Attach B(\ethis) on the RIGHT from A(\eobj)
// -------------------------------------------------------------------------------------------------
bool Object::attach_right_of( Object* obj, Association* id, bool unique /* = false */ )
{
    Object* bObj = this;

    // Relationship with myself only allowed
    // if association is enumerated object
    if ( !obj || ( obj == bObj && !id->is_enum_object( ) ) )
    {
        return false;
    }

    // B is right of A, so index of B is +1 of A
    id->next( );

    // Check if such association exists
    sea::ref< Link > b_link;

    // User must be pretty d-sure that there is no such association
    if ( !unique )
    {
        b_link = bObj->get_link( *id );
    }

    if ( b_link == NULL )
    {
        b_link = bObj->create_new_link( *id );

        // rc = 2 ( local, m_link ) - if the only link there
        // rc = 3 ( local, m_link, x-ref ) - if connected to other links
        if ( b_link == NULL )
        {
            ASSERT_DEBUG( false );
            return false;
        }
    }
    else
    {
        if ( b_link->left_link( ) != NULL )
        {
            // Allow polygamy - create new association with the same id
            b_link = bObj->create_new_link( *id );

            // rc = 2 ( local, m_link )
            if ( b_link == NULL )
            {
                ASSERT_DEBUG( false );
                return false;
            }
        }
    }

    // A is left of B, so index of A is -1 of B
    id->prev( );

    sea::ref< Link > a_link;

    // User must be pretty d-sure that there is no such association
    if ( !unique )
    {
        a_link = obj->get_link( *id );
    }

    if ( a_link == NULL )
    {
        a_link = obj->create_new_link( *id );

        // rc = 2 ( local, m_link ) - if the only link there
        // rc = 3 ( local, m_link, x-ref ) - if connected to other links
        if ( a_link == NULL )
        {
            ASSERT_DEBUG( false );
            return false;
        }
    }
    else
    {
        if ( a_link->right_link( ) != NULL )
        {
            // Allow polygamy - create new association with the same id
            a_link = obj->create_new_link( *id );

            // rc = 2 ( local, m_link )
            if ( a_link == NULL )
            {
                ASSERT_DEBUG( false );
                return false;
            }
        }
    }

    // Set links
    b_link->set_left_link( a_link.get( ) );
    a_link->set_right_link( b_link.get( ) );

    // Advance the enumerator
    id->next( );

    return true;
}

// -------------------------------------------------------------------------------------------------
// Attach B(\ethis) on the LEFT from A(\eobj)
// -------------------------------------------------------------------------------------------------
bool Object::attach_left_of( Object* obj, Association* id, bool unique /* = false */ )
{
    Object* bObj = this;

    // Relationship with myself only allowed
    // if association is enumerated object
    if ( !obj || ( obj == bObj && !id->is_enum_object( ) ) )
    {
        return false;
    }

    sea::ref< Link > b_link;

    // User must be pretty d-sure that there is no such association
    if ( !unique )
    {
        b_link = bObj->get_link( *id );
    }

    // Check if such association exists
    if ( b_link == NULL )
    {
        b_link = bObj->create_new_link( *id );

        // rc = 2 ( local, m_link ) - if the only link there
        // rc = 3 ( local, m_link, x-ref ) - if connected to other links
        if ( b_link == NULL )
        {
            ASSERT_DEBUG( false );
            return false;
        }
    }
    else
    {
        if ( b_link->right_link( ) != NULL )
        {
            // Allow polygamy - create new association with the same id
            b_link = bObj->create_new_link( *id );

            // rc = 3 ( local, m_link, x-ref )
            if ( b_link == NULL )
            {
                ASSERT_DEBUG( false );
                return false;
            }
        }
    }

    // A is right of B, so index of A is +1 of B
    id->next( );

    sea::ref< Link > a_link;

    // User must be pretty d-sure that there is no such association
    if ( !unique )
    {
        a_link = obj->get_link( *id );
    }

    if ( a_link == NULL )
    {
        a_link = obj->create_new_link( *id );

        // rc = 2 ( local, m_link ) - if the only link there
        // rc = 3 ( local, m_link, x-ref ) - if connected to other links
        if ( a_link == NULL )
        {
            ASSERT_DEBUG( false );
            return false;
        }
    }
    else
    {
        if ( a_link->left_link( ) != NULL )
        {
            // Allow polygamy - create new association with the same id
            a_link = obj->create_new_link( *id );

            // rc = 2 ( local, m_link )
            if ( a_link == NULL )
            {
                ASSERT_DEBUG( false );
                return false;
            }
        }
    }

    // Set links
    b_link->set_right_link( a_link.get( ) );
    a_link->set_left_link( b_link.get( ) );

    return true;
}

// -------------------------------------------------------------------------------------------------
// Insert B(\ethis) on the RIGHT from A(\eobj)
// -------------------------------------------------------------------------------------------------
bool Object::insert_right_of( Object* obj, Association* id )
{
    Object* bObj = this;
    if ( !obj || obj == bObj || id->is_enum_object( ) )
    {
        return false;
    }

    // Check if such association exists
    sea::ref< Link > b_link = bObj->get_link( *id );
    if ( b_link != NULL )
    {
        // Inserted alement has to be free of the associations
        return false;
    }

    b_link = bObj->create_new_link( *id );
    if ( b_link == NULL )
    {
        return false;
    }

    sea::ref< Link > a_link = obj->get_link( *id );
    if ( a_link == NULL )
    {
        a_link = obj->create_new_link( *id );
        if ( a_link == NULL )
        {
            ASSERT_DEBUG( false );
            return false;
        }
    }

    b_link->set_left_link( a_link.get( ) );

    sea::ref< Link > rightObjLink = a_link->right_link( );
    if ( rightObjLink != NULL )
    {
        b_link->set_right_link( rightObjLink.get( ) );
        rightObjLink->set_left_link( b_link.get( ) );
    }
    a_link->set_right_link( b_link.get( ) );

    return true;
}

// -------------------------------------------------------------------------------------------------
// Insert B(\ethis) on the LEFT from A(\eobj)
// -------------------------------------------------------------------------------------------------
bool Object::insert_left_of( Object* obj, Association* id )
{
    Object* bObj = this;
    if ( !obj || obj == bObj || id->is_enum_object( ) )
    {
        return false;
    }

    // Check if such association exists
    sea::ref< Link > b_link = bObj->get_link( *id );
    if ( b_link != NULL )
    {
        // Inserted alement has to be free of the associations
        return false;
    }

    b_link = bObj->create_new_link( *id );

    // rc = 2 ( local, m_link )
    if ( b_link == NULL )
    {
        ASSERT_DEBUG( false );
        return false;
    }

    sea::ref< Link > a_link = obj->get_link( *id );
    if ( a_link == NULL )
    {
        a_link = obj->create_new_link( *id );

        // rc = 2 ( local, m_link ) - if the only link there
        // rc = 3 ( local, m_link, x-ref ) - if connected to other links
        if ( a_link == NULL )
        {
            ASSERT_DEBUG( false );
            return false;
        }
    }

    b_link->set_right_link( a_link.get( ) );

    sea::ref< Link > leftObjLink = a_link->left_link( );
    if ( leftObjLink != NULL )
    {
        b_link->set_left_link( leftObjLink.get( ) );
        leftObjLink->set_right_link( b_link.get( ) );
    }

    a_link->set_left_link( b_link.get( ) );

    return true;
}

// -------------------------------------------------------------------------------------------------
// Remove an object from all associations specified by ID
// lo1 <- me -> ro1
// lo2 <- me -> ro2
// -------------------------------------------------------------------------------------------------
bool Object::detach( Association id )
{
    bool ret = false;

    // Get local reference to myself
    aref safe( this );
    sea::ref< Link > link;

    // Get rid of all associations with the same id
    // Get local reference of my links
    for( ;; )
    {
        link = get_link( id );
        if( !link )
        {
            break;
        }

        // Get local references of the neighbour's links
        sea::ref< Link > rnLink = link->right_link( );
        sea::ref< Link > lnLink = link->left_link( );

        Link* rn_link = rnLink.get( );
        Link* ln_link = lnLink.get( );

        // Assert on stray link object...
        ASSERT_DEBUG( rn_link || ln_link );

        sea::aref rnObj;
        sea::aref lnObj;

        // Rebuild right association
        if ( rn_link )
        {
            rnObj = rn_link->host( );
            ASSERT_DEBUG( rnObj.get( ) );

            ASSERT_DEBUG( rn_link->left_link( ) == link );
            ASSERT_DEBUG( rn_link->left_link( )->host( ) == this );

            rn_link->set_left_link( ln_link );
            link->set_right_link( NULL );

            // Forse right neighbor to destroy link object
            // IF there are no object connected to it by the
            // same association id
            rnObj->remove_link( rn_link );

        }

        // Rebuild left association
        if ( ln_link )
        {
            lnObj = ln_link->host( );
            ASSERT_DEBUG( lnObj.get( ) );

            ASSERT_DEBUG( ln_link->right_link( ) == link );
            ASSERT_DEBUG( ln_link->right_link( )->host( ) == this );

            ln_link->set_right_link( rn_link );
            link->set_left_link( NULL );

            // Forse left neighbor to destroy link object
            // IF there are no object connected to it by the
            // same association id
            lnObj->remove_link( ln_link );

        }

        // Now remove my link from objects associations
        remove_link( link.get( ) );
        ret = true;
    }

    return ret;
}


// -------------------------------------------------------------------------------------------------
// Detach object from another object by specified id
// lo1 <- me -> ro1
// lo2 <- me -> ro2
// -------------------------------------------------------------------------------------------------
bool Object::detach( Object* obj, Association id )
{
    if ( !obj )
    {
        return false;
    }

    // Get local reference to myself
    sea::aref safe( this );
    sea::ref< Link > link;

    // Go through all polygamy associations and search for
    // an object on either side - right or left
    for( VIterator< Object, Either > i( this, id ); *i; i++ )
    {
        link = i.get_link( );

        // Get local references of the neighbour's links
        sea::ref< Link > rnLink = link->right_link( );
        sea::ref< Link > lnLink = link->left_link( );

        Link* rn_link = rnLink.get( );
        Link* ln_link = lnLink.get( );

        // Assert on stray link object...
        ASSERT_DEBUG( rn_link || ln_link );

        sea::aref rnObj;
        sea::aref lnObj;

        // Check an object on the right
        if ( rn_link )
        {
            rnObj = rn_link->host( );
            ASSERT_DEBUG( rnObj.get( ) );

            ASSERT_DEBUG( rn_link->left_link( ) == link );
            ASSERT_DEBUG( rn_link->left_link( )->host( ) == this );

            if ( rnObj == obj )
            {
                rn_link->set_left_link( ln_link );

                // Clean up right association releasing
                // cross references between myself and the right obj
                link->set_right_link( NULL );

                // Forse right neighbor to destroy link object
                // IF there are no object connected to it by the
                // same association id
                rnObj->remove_link( rn_link );

                if( !ln_link )
                {
                    // Now remove my link from objects associations
                    remove_link( link.get( ) );
                }
                return true;
            }
        }

        // Check an object on the left
        if ( ln_link )
        {
            lnObj = ln_link->host( );
            ASSERT_DEBUG( lnObj.get( ) );

            ASSERT_DEBUG( ln_link->right_link( ) == link );
            ASSERT_DEBUG( ln_link->right_link( )->host( ) == this );

            if ( lnObj == obj )
            {
                ln_link->set_right_link( rn_link );
                // Clean up left association releasing
                // cross references between myself and the left obj
                link->set_left_link( NULL );

                // Forse left neighbor to destroy link object
                // IF there are no object connected to it by the
                // same association id
                lnObj->remove_link( ln_link );

                if( !rn_link )
                {
                    // Now remove my link from objects associations
                    remove_link( link.get( ) );
                }
                return true;
            }
        }
    }

    return false;
}


// -------------------------------------------------------------------------------------------------
// Remove link from object's associations
// -------------------------------------------------------------------------------------------------
inline void Object::remove_link( Link* link )
{
    // Right equals to the Left -> they are both NULL
    if ( link->right_link( ) != link->left_link( ) )
    {
        return;
    }

    ASSERT_DEBUG( link->right_link( ) == NULL );

    // Check if the link to be removed is the first one
    // in the linled associations
    if ( m_link == link )
    {
        m_link = link->down_link( Association( 0 ) );
    }

    // In common case may be multi dimentional associations.
    // So use detachAll
    link->detach_all( );
}


// -------------------------------------------------------------------------------------------------
// Check if object has a link with another object by the association
// -------------------------------------------------------------------------------------------------
bool Object::is_linked( Object* obj, Association id )
{
    if ( !obj )
    {
        return false;
    }

    // Get local reference to myself
    sea::aref safe( this );
    sea::ref< Link > link;

    // Go through all polygamy associations and search for
    // an object on either side - right or left
    for( VIterator< Object, Either > i( this, id ); *i; i++ )
    {
        link = i.get_link( );

        // Get local references of the neighbour's links
        sea::ref< Link > rnLink = link->right_link( );
        sea::ref< Link > lnLink = link->left_link( );

        Link* rn_link = rnLink.get( );
        Link* ln_link = lnLink.get( );

        // Assert on stray link object...
        ASSERT_DEBUG( rn_link || ln_link );

        sea::aref rnObj;
        sea::aref lnObj;

        // Check an object on the right
        if ( rn_link )
        {
            rnObj = rn_link->host( );
            ASSERT_DEBUG( rnObj.get( ) );

            ASSERT_DEBUG( rn_link->left_link( ) == link );
            ASSERT_DEBUG( rn_link->left_link( )->host( ) == this );

            if ( rnObj == obj )
            {
                return true;
            }
        }

        // Check an object on the left
        if ( ln_link )
        {
            lnObj = ln_link->host( );
            ASSERT_DEBUG( lnObj.get( ) );

            ASSERT_DEBUG( ln_link->right_link( ) == link );
            ASSERT_DEBUG( ln_link->right_link( )->host( ) == this );

            if ( lnObj == obj )
            {
                return true;
            }
        }
    }

    return false;
}

// -------------------------------------------------------------------------------------------------
// Get link of link object by ID
// -------------------------------------------------------------------------------------------------
sea::ref< Link > Object::get_link( Association id )
{
    // Return first link if association is not specified.
    if ( id.is_any( ) )
    {
        return m_link;
    }

    // Going through all associations for this object
    sea::ref< Link > a_link = m_link;
    Association link_association( 0 );

    for( ; a_link != NULL ;
         a_link = a_link->down_link( link_association ) )
    {
        if( a_link->get_association( ) == id )
        {
#if defined( USE_MRU_ASSOCIATIONS )

            // Move accessed link first in the list to speed up next
            // requests
            if( m_link != linkObj )
            {
                a_link->detach( link_association );
                a_link->attach_left_of( m_link, link_association );
                m_link = a_link;
            }
#endif
            return a_link;
        }
    }
    return sea::ref< Link >( );
}

// -------------------------------------------------------------------------------------------------
// Create new link
// -------------------------------------------------------------------------------------------------
sea::ref< Link > Object::create_new_link( Association id )
{
    // Create new association with its own ID
    // Local rc = birth rc
    Link* newlink = NEW Link( this, id );
    if ( !newlink )
    {
        ASSERT_DEBUG( false );
        return NULL;
    }
    sea::ref< Link > link( newlink, false );

    // Place most recent link first
    // Link object association is 0
    newlink->attach_left_of( m_link.get( ), Association( 0 ) );

    // Make it root link for the object
    m_link = link;

    // Birth reference is passed to requestor
    // to use( dispose or store )
    return link;
}

// -------------------------------------------------------------------------------------------------
// Get Right neighbour
// -------------------------------------------------------------------------------------------------
sea::ref< > Object::get_right_obj( Association id )
{
    sea::ref< Link > link = get_link( id );
    if ( !link )
    {
        return NULL;
    }

    link = link->right_link( );
    if ( !link )
    {
        return NULL;
    }

    return link->host( );
}


// -------------------------------------------------------------------------------------------------
// Get Left neighbour
// -------------------------------------------------------------------------------------------------
sea::ref< > Object::get_left_obj( Association id )
{
    sea::ref< Link > link = get_link( id );
    if ( !link )
    {
        return NULL;
    }

    link = link->left_link( );
    if ( !link )
    {
        return NULL;
    }

    return link->host( );
}

// -------------------------------------------------------------------------------------------------
// Box a string
// -------------------------------------------------------------------------------------------------
void aref::operator << ( char* name )
{
    adopt( NEW Object( name ) );
}

// -------------------------------------------------------------------------------------------------
// Box a const string
// -------------------------------------------------------------------------------------------------
void aref::operator << ( const char* name )
{
    adopt( NEW Object( name ) );
}

// -------------------------------------------------------------------------------------------------
// Box an integer
// -------------------------------------------------------------------------------------------------
void aref::operator << ( i::uint32 number )
{
    adopt( NEW Object( number ) );
}

// -------------------------------------------------------------------------------------------------
